<?php

abstract class Model_Abstract
{
    protected $_connection = null;
    
	protected $_validation = null;
    /*******for $attributes********/
	protected $_validationError = array();
	/*******for $attributesArray********/
	protected $_validationArrayError = array();
	
	protected $_values = array();
	protected $_query = array();
	protected $_vars = array();
	
	protected $_constraintFind = array('groupBy' => array(), 'onlyOne' => false, 'distinct' => false, 'stripName' => false, 'setResultKey' => false);
	
	protected $_notFilterScriptTagsAttributes = array();
	
	public $tableName = null;
	public $primaryKey = 'id';
    public $attributes = null;
    public $attributesArray = array();
        
    protected static $_models=array();
    
    public function __construct()
    {
        if ($this->_connection == null) {
        	// Get the Connection
        	$this->_connection = Connection::getInstance();
        }
        
        // Model init function
        $this->init();
    }
    
    public function init()
    {

    }
    
	public static function model($className = __CLASS__)
	{
		if(isset(self::$_models[$className]))
			return self::$_models[$className];
		else
		{
			self::$_models[$className] = new $className(null);
			return self::$_models[$className];
		}
	}
    
    public function attributeLabels()
	{
	
	}
	public function getAttributeLabel($attribute)
	{
		$attributeLabels = $this->attributeLabels();
		return isset($attributeLabels[$attribute])? $attributeLabels[$attribute] : $attribute;
	}
    
	protected function _before_validate()
	{
		$this->_validationError = array();
	}
	
	protected function _validate()
	{
		$ret = true;
		if(empty($this->attributes)){
			$this->_validationError['attributes'] = 'attributes is still null';
			return false;	
		}
		
		foreach($this->attributes as $name => $v){
			if(isset($this->_validation[$name])){
				foreach($this->_validation[$name] as $rule){
					$ruleName = 'validate_' . $rule[0];
					$ruleMessage = $rule[1];
					$ruleVal = isset($rule[2])? $rule[2] : null;
					if($ruleName == 'validate_same'){
						$ruleVal = $this->attributes[$ruleVal];
					}
					$result = Validator::$ruleName($v, $ruleVal);
					if(!$result){
						$ret = false;
						$name = $this->getAttributeLabel($name);
						$this->_validationError[$name][] = $ruleMessage;
					}
				}
			}
		}
		return $ret;
	}
	
	/**
	 * 
	 * @param int $limitCount 允许数据库中拥有重复数据的数量，默认0（即唯一）
	 */
	protected function _attributesIdentity($names = array(), $limitCount = 0)
	{
		$ret = true;
		if(!empty($names)){
			foreach($names as $name){
				if(isset($this->attributes[$name])){
					if($this->count(array($name => $this->attributes[$name])) > $limitCount){
						$this->_validationError[$name][] = $name . '已存在';
						$ret = false;
					}
				}
			}
		}
		
		return $ret;
	}
	
	protected function _before_validate_array()
	{
		$this->_validationArrayError = array();
	}
	
	protected function _validate_array()
	{
		$ret = true;
		if(empty($this->attributesArray)){
			$this->_validationArrayError['attributesArray'] = 'attributesArray is still null';
			return false;	
		}
		
		foreach($this->attributesArray as $k => $attributes){
			if(empty($attributes)){
				$this->_validationError['attributesArray'][$k] = 'attributes is still null';
				return false;	
			}
			
			foreach($attributes as $name => $v){
				if(isset($this->_validation[$name])){
					foreach($this->_validation[$name] as $rule){
						$ruleName = 'validate_' . $rule[0];
						$ruleMessage = $rule[1];
						$ruleVal = isset($rule[2])? $rule[2] : null;
						if($ruleName == 'validate_same'){
							$ruleVal = $this->attributes[$ruleVal];
						}
						$result = Validator::$ruleName($v, $ruleVal);
						if(!$result){
							$ret = false;
							$name = $this->getAttributeLabel($name);
							$this->_validationArrayError[$k][$name][] = $ruleMessage;
						}
					}
				}
			}
		}
		
		return $ret;
	}

	public function getValidateError()
	{
		return $this->_validationError;
	}
	
	public function getValidateArrayError()
	{
		return $this->_validationArrayError;
	}
	
	
	protected function _filterAttributesScriptTags()
	{
		if(!empty($this->attributes)){
			foreach($this->attributes as $k => $v){
				if(!in_array($k, $this->_notFilterScriptTagsAttributes)){
					$v = preg_replace(					//change all script tags to <xxx></xxx>
						"/(<\/?)(script)([^>]*>)/e", 
						"'\\1xxx\\3'", 
						$v
					);
					$this->attributes[$k] = $v;
				}
			}
		}
	}
	protected function _before_sql()
	{
		$this->_values = array();
		$this->_query = array();
		$this->_vars = array();
	}
	protected function _save($validate = true)
	{
		if($validate){
			if(!$this->validate()){
				return false;
			}
		}
		
		$pk = null;
		foreach($this->attributes as $name => $val){
			if($name == $this->primaryKey){
				$pk = $val;
			}else{
				$this->_query[] = "{$name}=?";
				$this->_vars[] = $val;			
			}

		}
		
		if($pk){
			$this->_vars[] = $pk;
			$sql = ' UPDATE ' . $this->tableName . ' SET ' . implode(', ', $this->_query) . ' WHERE ' . $this->primaryKey . '=? ';
		}else{
			$sql = ' INSERT INTO ' . $this->tableName . ' SET ' . implode(', ', $this->_query);
		}
		return $this->_connection->execute($sql, $this->_vars);
	}
	
	protected function _update($setFields = array(), $attributes = array(), $conditions = array(), $params = array())
	{
		if(empty($this->tableName)){
			return false;
		}
			
		if(!is_array($conditions) && !empty($conditions)){
			$conditions = array($conditions);
		}
		
		if(!is_array($params) && !empty($params)){
			$params = array($params);
		}
		
		$set = array();
		if(!empty($setFields)){
			foreach($setFields as $name => $val){
				$set[] = $name . '=?';
				$this->_vars[] = $val;
			}
		}
		
		if(!empty($attributes)){
			foreach($attributes as $name => $val){
				if(is_array($val)){
					$this->_query[] = '(' . $name . ' IN ("' . implode('","', $val) . '"))';
				}else{
					$this->_query[] = '(' . $name . '=?)';
					$this->_vars[] = $val;
				}
			}
		}
		
		if(!empty($conditions)){
			foreach($conditions as $k => $condition){
				$this->_query[] = '(' . $condition . ')';
				$this->_vars[] = $params[$k];
			}
		}
			
		$sqlWhere = '';
		if(!empty($this->_query)){
			$sqlWhere = ' WHERE ' . implode(' AND ', $this->_query);
		}
		
		$sql = " UPDATE {$this->tableName} SET " . implode(', ', $set) . $sqlWhere;
		return $this->_connection->execute($sql, $this->_vars);
	}
	
	protected function _delete($attributes = array(), $conditions = array(), $params = array())
	{
		if(!is_array($conditions) && !empty($conditions)){
			$conditions = array($conditions);
		}
		
		if(!empty($attributes)){
			foreach($attributes as $name => $val){
				$this->_query[] = '(' . $name . '=?)';
				$this->_vars[] = $val;
			}
		}
		
		if(!empty($conditions)){
			foreach($conditions as $k => $condition){
				$this->_query[] = '(' . $condition . ')';
				$this->_vars[] = $params[$k];
			}
		}
		
		$sql = ' DELETE FROM ' . $this->tableName . ' WHERE ' . implode(' AND ', $this->_query);
		$result = $this->_connection->execute($sql, $this->_vars);
		return $result;
	}
	
	/**
	 * 以$attributesArray[0]中的键值作为table(key1, key2...), 注意后面元素中的键值关系与此对应，空值留空占位
	 **/
	protected function _saveArray($validate = true)
	{
		if($validate){
			if(!$this->validate_array()){
				return false;
			}
		}
		
		foreach($this->attributesArray as $attributes){
			$q = array();
			foreach($attributes as $name => $val){
				$q[] = '?';
				$this->_vars[] = $val;
			}
			$this->_values[] = '(' . implode(', ', $q) . ')';
		}
		$sql = ' INSERT INTO ' . $this->tableName . '(' . implode(', ', array_keys($this->attributesArray[0])) . ') VALUES ' . implode(', ', $this->_values);
		
		return $this->_connection->execute($sql, $this->_vars);
	}
	
	protected function _find($fields = '*', $attributes = array(), $conditions = array(), $params = array(), $constraint = array())
	{
		if(empty($this->tableName)){
			return false;
		}
		$c = array_merge(
			$this->_constraintFind,
			$constraint
		);
		extract($c);
		
		if(!is_array($fields) && !empty($fields)){
			$fields = array($fields);
		}
		
		if(!is_array($conditions) && !empty($conditions)){
			$conditions = array($conditions);
		}
		
		if(!is_array($params) && !empty($params)){
			$params = array($params);
		}
		
		if(!is_array($groupBy) && !empty($groupBy)){
			$groupBy = array($groupBy);
		}
		
		if(empty($fields)){
			return false;
		}
		
		//$fields = $this->fieldsSafe($fields);
		
		if(!empty($attributes)){
			foreach($attributes as $name => $val){
				if(is_array($val)){
					$this->_query[] = '(' . $name . ' IN ("' . implode('","', $val) . '"))';
				}else{
					$this->_query[] = '(' . $name . '=?)';
					$this->_vars[] = $val;
				}
			}
		}
		
		if(!empty($conditions)){
			foreach($conditions as $k => $condition){
				$this->_query[] = '(' . $condition . ')';
				$this->_vars[] = $params[$k];
			}
		}
		
		$sqlGroup = '';
		if(!empty($groupBy)){
			$sqlGroup = ' GROUP BY ' . implode(', ', $groupBy);
		}
		
		$sqlWhere = '';
		if(!empty($this->_query)){
			$sqlWhere = ' WHERE ' . implode(' AND ', $this->_query);
		}
		
		$sql = ' SELECT ' . ($distinct? ' DISTINCT ' : '') . implode(', ', $fields) . ' FROM ' . $this->tableName . $sqlWhere . $sqlGroup;
		$result = $this->_connection->query($sql, $this->_vars);
		
		if(count($result) > 0){
			if($setResultKey){
				$result = $this->setResultKey($result, $setResultKey);
			}
			
			if($stripName){
				foreach($result as $k => $r){
					$result[$k] = array_pop($r);
				}
			}
	
			if($onlyOne){
				$result = $result[0];
			}
		}
		return $result;
	}
	
	protected function _count($attributes = array(), $conditions = array(), $params = array())
	{
		if(empty($this->tableName)){
			return false;
		}
		
		if(!is_array($conditions) && !empty($conditions)){
			$conditions = array($conditions);
		}
		
		if(!is_array($params) && !empty($params)){
			$params = array($params);
		}
		
		
		//$fields = $this->fieldsSafe($fields);
		
		if(!empty($attributes)){
			foreach($attributes as $name => $val){
				$this->_query[] = '(' . $name . '=?)';
				$this->_vars[] = $val;
			}
		}
		if(!empty($conditions)){
			foreach($conditions as $k => $condition){
				$this->_query[] = '(' . $condition . ')';
				$this->_vars[] = $params[$k];
			}
		}
		
		$sql = ' SELECT COUNT(*) AS count FROM ' . $this->tableName . ' WHERE ' . implode(' AND ', $this->_query);
		$result = $this->_connection->query($sql, $this->_vars);
		
		return $result[0]['count'];
	}
	
	protected function fieldsSafe($fields)
	{
		if(is_array($fields)){
			foreach($fields as $n => $field){
				$fields[$n] = '`'.$field.'`';
			}
		}else{
			$fields = '`'.$fields.'`';
		}
		
		return $fields;
	}
	
	
	public function __call($name, $arguments)
	{
		$name = '_' . $name;
	    if(method_exists($this, $name)){
	    	if(in_array($name, array('_validate', '_attributesIdentity'))){
	    		$this->_before_validate();
	    	}else if(in_array($name, array('_validate_array'))){
	    		$this->_before_validate_array();
	    	}else if(in_array($name, array('_save', '_delete', '_saveArray', '_find', '_count', '_update'))){
	    		$this->_before_sql();
	    		$this->_filterAttributesScriptTags();
	    	}
    		try{
    			return call_user_func_array(array(&$this, $name), $arguments);
    		}catch (Exception $e){
				QLog::log(__CLASS__.' '.__METHOD__.' --- '.$e->getMessage(), QLog::ERR);
				throw new Model_Exception($e->getMessage(), $e->getCode());
			}
    	}else{
    		throw new Model_Exception("Call to undefined method ".__CLASS__."::".$name."()");
    	}
	}
	
	public function setColumnAsKey($result, $cloumn = 'id') {
		$temp = array();
		if (count($result) > 0) {
			foreach ($result as $resultItem) {
				foreach ($resultItem as $k => $v) {
					if ($cloumn == $k) {
						unset($resultItem['date']);
						$temp[$v] = $resultItem;
					}
				}
			}
		}
		return $temp;
	}
	
	public function setResultKey($sqlResult, $using = '', $multi = false, $saveUsing = true) {
		$temp = array();
		if (count($sqlResult) > 0) {
			foreach ($sqlResult as $resultItem) {
				foreach ($resultItem as $k => $v) {
					if ($using == $k) {
						if(!$saveUsing){
							unset($resultItem[$using]);
						}
						if($multi){
							$temp[$v][] = $resultItem;
						}else{
							$temp[$v] = $resultItem;
						}
					}
				}
			}
		}
		return $temp;
	}
	
}
